package NGLR;

import GameLogic.Background;
import GameLogic.EntityCoordinate;
import GameLogic.EntityModel;
import Renderer.*;
import org.lwjgl.LWJGLException;
import org.lwjgl.LWJGLUtil;
import org.lwjgl.Sys;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;

import static org.lwjgl.opengl.GL12.*;
import utilities.Image;
import utilities.TextureDecoder;

import static org.lwjgl.util.glu.GLU.gluPerspective;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.ARBTextureRectangle.*;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static java.lang.System.err;

public class NGLR implements Renderer{
    private RendererSettings rendering_settings;
    private ProjectionSettings projection_settings;
    private double aspect_ratio;
    private int max_texture_size;
    private ArrayList<Model> models_array;
    private ArrayList<BackgroundRendering> backgrounds_array;

    public NGLR(RendererSettings settings){

        this.rendering_settings = settings;

        try{
                Display.setFullscreen(settings.WindowFullscreen);
                Display.setResizable(settings.WindowResizable);
                Display.setVSyncEnabled(settings.VSync);
                Display.sync(settings.FpsCap);
                Display.setTitle(settings.WindowTitle);
                Display.setDisplayMode(new DisplayMode(settings.WindowXres, settings.WindowYres));
                Display.create();
        }
        catch (LWJGLException ex){
                ex.printStackTrace();
                err.println("Error while initializing display");
        }
        err.println("LWJGL: " + Sys.getVersion() + " / " + LWJGLUtil.getPlatformName());
        err.println("GL_VENDOR: " + glGetString(GL_VENDOR));
        err.println("GL_RENDERER: " + glGetString(GL_RENDERER));
        err.println("GL_VERSION: " + glGetString(GL_VERSION));
        err.println("Max texture size: " + ( max_texture_size = glGetInteger(GL_MAX_TEXTURE_SIZE)));
        err.println("Max rectangular texture size: " + glGetInteger(GL_MAX_RECTANGLE_TEXTURE_SIZE_ARB));
        aspect_ratio = (double)Display.getWidth()/ (double)(Display.getHeight() == 0 ? 1 : Display.getHeight());
        models_array = new ArrayList<Model>();
        backgrounds_array =  new ArrayList<BackgroundRendering>();

        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        glEnableClientState(GL_COLOR_ARRAY);
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glEnable(GL_TEXTURE_2D);
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD);

     //   glEnable(GL_DEPTH_TEST);
    //    glDepthMask(true);
     //   glDepthFunc(GL_LEQUAL);
      //  glDepthRange(-1.0, 1.0);
     //   glClearDepth(1.0);
     //   glEnable(GL_ALPHA_TEST);
     //   glAlphaFunc(GL_GEQUAL, 0.01f);
    }

    @Override
    public int LoadObject(EntityModel Model) {

        int put_position = -1;
        for(int i =0; i < models_array.size(); i++){
            if(models_array.get(i) == null){
                put_position = i;
                break;
            }
        }
        if(put_position == -1)
            put_position = models_array.size();

        Model ready_model = new Model(Model);
        if(Model.texture_path != null){
            glGenTextures(ready_model.texures_IDs);
            if(Model.frames == 1){
                Image texture = TextureDecoder.LoadImage(Model.texture_path);
                if(texture == null){
                    err.println("NGLR: Error while loading texture: " + Model.texture_path);
                }
                glBindTexture(GL_TEXTURE_2D, ready_model.texures_IDs.get(0));
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
                glTexImage2D(GL_TEXTURE_2D,0 ,GL_RGBA8, texture.width, texture.height, 0, texture.format, GL_UNSIGNED_BYTE, texture.data);
            }
            if(Model.frames > 1) {
                ArrayList<Image> textures = TextureDecoder.LoadSprites(Model.texture_path, ".png", Model.frames);
                for(int i = 0; i < Model.frames; i++){
                    glBindTexture(GL_TEXTURE_2D, ready_model.texures_IDs.get(i));
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
                    glTexImage2D(GL_TEXTURE_2D,0 ,GL_RGBA8, textures.get(i).width, textures.get(i).height, 0, textures.get(i).format, GL_UNSIGNED_BYTE, textures.get(i).data);
                }
            }
        }
        models_array.add(put_position, ready_model);


        return put_position;
    }

    @Override
    public double GetAspectRatio() {
        return aspect_ratio;
    }

    @Override
    public void DrawFrame(List<EntityCoordinate> Coordinates1, double ScreenCentreX, double ScreenCentreY) {
        //Collections.sort(Coordinates);
        //dump_coordinates(Coordinates1);
         ArrayList<EntityCoordinate> Coordinates = new ArrayList<EntityCoordinate>(Coordinates1);
         Collections.sort(Coordinates);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        for(int i = 0; i < backgrounds_array.size(); i++)
            backgrounds_array.get(i).Draw(ScreenCentreX, aspect_ratio, getYCoordRange(backgrounds_array.get(i).getZ()));


        for(int i = 0; i < Coordinates.size(); i++){
            int ID = Coordinates.get(i).modelID;
            glPushMatrix();
            if(aspect_ratio < 1.0)
                glTranslated((Coordinates.get(i).posX - ScreenCentreX) * aspect_ratio,
                    (Coordinates.get(i).posY - ScreenCentreY) * aspect_ratio,
                    Coordinates.get(i).posZ);
            else
                 glTranslated((Coordinates.get(i).posX - ScreenCentreX) / aspect_ratio,
                    (Coordinates.get(i).posY - ScreenCentreY) / aspect_ratio,
                    Coordinates.get(i).posZ);

            glRotatef(Coordinates.get(i).angle - 90, 0.0f, 0.0f, 1.0f);
            glScalef(Coordinates.get(i).scaleX, Coordinates.get(i).scaleY, 1.0f);

            if((models_array.get(Coordinates.get(i).modelID).texures_IDs != null) && (models_array.get(Coordinates.get(i).modelID).texture_coordinate_array != null)){
                glBindTexture(GL_TEXTURE_2D, models_array.get(Coordinates.get(i).modelID).texures_IDs.get(Coordinates.get(i).animFrame));
                glTexCoordPointer(2, 0, models_array.get(Coordinates.get(i).modelID).texture_coordinate_array) ;
            }
            if((models_array.get(Coordinates.get(i).modelID).texures_IDs != null) &&  (models_array.get(Coordinates.get(i).modelID).texture_coordinate_array == null)){
                glEnable(GL_TEXTURE_GEN_S);
                glEnable(GL_TEXTURE_GEN_T);
                glEnable(GL_TEXTURE_GEN_R);
                glEnable(GL_TEXTURE_GEN_Q);
            }
            glVertexPointer(2, 0, models_array.get(Coordinates.get(i).modelID).vertex_array);
            glColorPointer(4, 0, models_array.get(Coordinates.get(i).modelID).vertex_colour_array);

            glDrawArrays(GL_QUADS, 0, 4);

            //if((models_array.get(Coordinates.get(i).modelID).texures_IDs != null) && (models_array.get(Coordinates.get(i).modelID).texture_coordinate_array != null) ){
            //    glBindTexture(GL_TEXTURE_2D, 0);
           // }
            if((models_array.get(Coordinates.get(i).modelID).texures_IDs != null) &&  (models_array.get(Coordinates.get(i).modelID).texture_coordinate_array == null)){
                glDisable(GL_TEXTURE_GEN_S);
                glDisable(GL_TEXTURE_GEN_T);
                glDisable(GL_TEXTURE_GEN_R);
                glDisable(GL_TEXTURE_GEN_Q);
            }
            glPopMatrix();
        }
        Display.update();
        if(Display.wasResized())
            LoadProjectionSettings(projection_settings);
    }

    @Override
    public boolean DeleteModel(int ID) {
        if(models_array.size() <= ID || models_array.get(ID) == null)
            return false;
        Model mod = models_array.get(ID);
        glDeleteTextures(mod.texures_IDs);
        models_array.remove(ID);
        return true;
    }

    @Override
    public void DrawWindows(List<Window> windows) {
        //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public void Cleanup() {
        //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public int LoadFont(String fontPath) {
        return 0;  //To change body of implemented methods use File | Settings | File Templates.
    }

    public boolean DeleteFont(int FontID) {
        return false;  //To change body of created methods use File | Settings | File Templates.
    }

    @Override
    public void DrawHud(HUD hud) {
        //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public void LoadSettings(RendererSettings settings) {
        //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public void LoadProjectionSettings(ProjectionSettings settings) {
        projection_settings = settings;
        aspect_ratio = (double)Display.getWidth()/ (double)(Display.getHeight() == 0 ? 1 : Display.getHeight());
        glViewport(0,0,Display.getWidth(), Display.getHeight());
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluPerspective(settings.Angle, (float) aspect_ratio, settings.ZNear, settings.ZFar);
        glTranslatef(settings.CameraX, settings.CameraY, settings.CameraZ);
        glRotatef(settings.Rotarion, 0, 0, 1);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

    }

    @Override
    public int LoadBackground(Background background) {

        int put_position = -1;
        for(int i =0; i < backgrounds_array.size(); i++){
            if(backgrounds_array.get(i) == null){
                put_position = i;
                break;
            }
        }
        if(put_position == -1)
            put_position = backgrounds_array.size();


        backgrounds_array.add(put_position, new BackgroundRendering(background));
        return put_position;
    }


    @Override
    public boolean DeleteBackgrund(int BackgroundID) {
        if (backgrounds_array.size() >= BackgroundID || backgrounds_array.get(BackgroundID) == null)
            return false;
        BackgroundRendering bg = backgrounds_array.get(BackgroundID);
        bg.Clean();
        backgrounds_array.remove(BackgroundID);
        return true;
    }

    private void dump_coordinates(List<EntityCoordinate> lista){
     //   for(int i = 0; i < lista.size(); i++)
       //     System.err.println("RLIST :  " + i + " ID: " + lista.get(i).modelID);
    }

    public double getXCoordRange(double Zpos){
        return Math.tan(projection_settings.Angle * Math.PI / 180.0) * (Zpos - projection_settings.CameraZ);
    }

    public double getYCoordRange(double Zpos){
        return getXCoordRange(Zpos) / aspect_ratio;
    }
}
